package com.mori.encrypt.spring.boot.core.init;

import com.mori.encrypt.spring.boot.core.annotation.Decrypt;
import com.mori.encrypt.spring.boot.core.annotation.Encrypt;
import com.mori.encrypt.spring.boot.core.constant.HttpMethodTypePrefixConstant;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.core.env.AbstractEnvironment;
import org.springframework.core.env.EnumerablePropertySource;
import org.springframework.core.env.Environment;
import org.springframework.core.env.PropertySource;
import org.springframework.stereotype.Controller;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.*;

import java.lang.reflect.Method;
import java.util.*;

/**
 * <p> 加密解密数据初始化 </p>
 *
 * @author mori
 * @since 2020/9/29 16:11
 */
public class EncryptDataInit implements ApplicationContextAware {

	private Logger logger = LoggerFactory.getLogger(ApplicationContextAware.class);

	private static String SLASH = "/";

	/**
	 * 需要对请求内容进行解密的接口URI<br>
	 * 比如：/user/list<br>
	 * 不支持@PathVariable格式的URI
	 */
	private List<String> requestDecryptUriList = new ArrayList<>();

	/**
	 * 需要对响应内容进行加密的接口URI<br>
	 * 比如：/user/list<br>
	 * 不支持@PathVariable格式的URI
	 */
	private List<String> responseEncryptUriList = new ArrayList<>();

	/**
	 * Url参数需要解密的配置
	 * 比如：/user/list?name=加密内容<br>
	 * 格式：Key API路径  Value 需要解密的字段
	 * 示列：/user/list  [name,age]
	 */
	private Map<String, List<String>> requestDecryptParamMap = new HashMap<>();

	private String contextPath;


	public List<String> getRequestDecryptUriList() {
		return requestDecryptUriList;
	}

	public void setRequestDecryptUriList(List<String> requestDecryptUriList) {
		this.requestDecryptUriList = requestDecryptUriList;
	}

	public List<String> getResponseEncryptUriList() {
		return responseEncryptUriList;
	}

	public void setResponseEncryptUriList(List<String> responseEncryptUriList) {
		this.responseEncryptUriList = responseEncryptUriList;
	}

	public Map<String, List<String>> getRequestDecryptParamMap() {
		return requestDecryptParamMap;
	}

	public void setRequestDecryptParamMap(Map<String, List<String>> requestDecryptParamMap) {
		this.requestDecryptParamMap = requestDecryptParamMap;
	}

	public String getContextPath() {
		return contextPath;
	}

	public void setContextPath(String contextPath) {
		this.contextPath = contextPath;
	}

	@Override
	public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
		this.contextPath = applicationContext.getEnvironment().getProperty("server.servlet.context-path");
		// 获取所有controller对象
		Map<String, Object> beanMap = applicationContext.getBeansWithAnnotation(Controller.class);
		// 初始化接口加密解密配置
		initData(beanMap);
		// 初始化url加密配置
		initRequestDecryptParam(applicationContext.getEnvironment());
	}

	/**
	 * 初始化Url 参数解密配置
	 *
	 * @param environment
	 */
	private void initRequestDecryptParam(Environment environment) {
		for (Iterator it = ((AbstractEnvironment) environment).getPropertySources().iterator(); it.hasNext(); ) {
			PropertySource propertySource = (PropertySource) it.next();
			if (propertySource instanceof EnumerablePropertySource) {
				for (String name : ((EnumerablePropertySource) propertySource).getPropertyNames()) {
					// #encrypt.request-decrypt-param-map.get$/encryptStr=name
					if (name.startsWith("encrypt.request-decrypt-param-map")) {
						String[] keys = name.split("\\.");
						String key = keys[keys.length - 1];
						String property = environment.getProperty(name);
						requestDecryptParamMap.put(key.replace("$", ":"), Arrays.asList(property.split(",")));
					}
				}
			}
		}
	}

	private void initData(Map<String, Object> beanMap) {
		if (beanMap == null) {
			return;
		}
		for (Object bean : beanMap.values()) {
			Class<?> clz = bean.getClass();
			Method[] methods = clz.getMethods();
			for (Method method : methods) {
				this.initUrlConfig(clz, method);
			}
		}
	}

	/**
	 * 初始化路径配置
	 *
	 * @param clz
	 * @param method
	 */
	private void initUrlConfig(Class<?> clz, Method method) {
		Encrypt encrypt = AnnotationUtils.findAnnotation(method, Encrypt.class);
		if (encrypt != null) {
			// 注解中的URI优先级高
			String uri = encrypt.value();
			if (!StringUtils.hasText(uri)) {
				uri = getApiUri(clz, method);
			}
			logger.debug("Encrypt URI: {}", uri);
			responseEncryptUriList.add(uri);
		}
		Decrypt decrypt = AnnotationUtils.findAnnotation(method, Decrypt.class);
		if (decrypt != null) {
			String uri = decrypt.value();
			if (!StringUtils.hasText(uri)) {
				uri = getApiUri(clz, method);
			}
			String decryptParam = decrypt.decryptParam();
			if (StringUtils.hasText(decryptParam)) {
				requestDecryptParamMap.put(uri, Arrays.asList(decryptParam.split(",")));
			}

			logger.debug("Decrypt URI: {}", uri);
			requestDecryptUriList.add(uri);
		}
	}

	/**
	 * 获得接口路径
	 *
	 * @param clz
	 * @param method
	 * @return
	 */
	private String getApiUri(Class<?> clz, Method method) {
		String methodType = "";
		StringBuilder uri = new StringBuilder();

		RequestMapping reqMapping = AnnotationUtils.findAnnotation(clz, RequestMapping.class);
		if (reqMapping != null) {
			uri.append(formatUri(reqMapping.value()[0]));
		}
		GetMapping getMapping = AnnotationUtils.findAnnotation(method, GetMapping.class);
		PostMapping postMapping = AnnotationUtils.findAnnotation(method, PostMapping.class);
		RequestMapping requestMapping = AnnotationUtils.findAnnotation(method, RequestMapping.class);
		PutMapping putMapping = AnnotationUtils.findAnnotation(method, PutMapping.class);
		DeleteMapping deleteMapping = AnnotationUtils.findAnnotation(method, DeleteMapping.class);

		if (getMapping != null) {
			methodType = HttpMethodTypePrefixConstant.GET;
			uri.append(formatUri(getMapping.value()[0]));

		} else if (postMapping != null) {
			methodType = HttpMethodTypePrefixConstant.POST;
			uri.append(formatUri(postMapping.value()[0]));

		} else if (putMapping != null) {
			methodType = HttpMethodTypePrefixConstant.PUT;
			uri.append(formatUri(putMapping.value()[0]));

		} else if (deleteMapping != null) {
			methodType = HttpMethodTypePrefixConstant.DELETE;
			uri.append(formatUri(deleteMapping.value()[0]));

		} else if (requestMapping != null) {
			RequestMethod requestMethod = RequestMethod.GET;
			if (requestMapping.method().length > 0) {
				requestMethod = requestMapping.method()[0];
			}
			methodType = requestMethod.name().toLowerCase() + ":";
			uri.append(formatUri(requestMapping.value()[0]));
		}

		if (StringUtils.hasText(this.contextPath) && !SLASH.equals(this.contextPath)) {
			if (this.contextPath.endsWith(SLASH)) {
				this.contextPath = this.contextPath.substring(0, this.contextPath.length() - 1);
			}
			return methodType + this.contextPath + uri.toString();
		}

		return methodType + uri.toString();
	}

	private String formatUri(String uri) {
		if (uri.startsWith(SLASH)) {
			return uri;
		}
		return SLASH + uri;
	}
}
